#include "pch.h"
#include "CscanFileBuilder.h"
#include <iostream>
#include <utility>
#include <Windows.h>
#include <fstream>
#include <filesystem>
#include <ppl.h>
#include <vector>
#include <chrono>
#include "finally.h"
#include <filesystem>

namespace chrono = std::chrono;
void CscanFileBuilder::flip_endian(const uint32_t* original, uint32_t* fliped, int size) const
{
	concurrency::parallel_for(0, size, [&original, &fliped](int i)
		{
			fliped[i] = (original[i] << 24) | (original[i] >> 24) | (original[i] << 8 & 0x00ff0000) | (original[i] >> 8 & 0x0000ff00);
		});
}

void CscanFileBuilder::create_info_file(const std::string& path, const cscan_info& info)
{
	auto f = std::fstream(path, std::ios::out);
	auto final = finally([&]
		{
			f.close();
		});
	f << "[DataType]" << std::endl << "DataType = 5" << std::endl << std::endl;
	f << "[Dimensions]" << std::endl;
	f << "X = " << info.size.x << std::endl;
	f << "Y = " << info.size.y << std::endl;
	f << "Z = " << info.size.z << std::endl << std::endl;
	f << "[Header]" << std::endl << "Header = 0" << std::endl << std::endl;
	f << "[Endianess]" << std::endl << "Endianess = 0" << std::endl << std::endl;
	f << "[VoxelSize]" << std::endl;
	f << "X = " << info.scale.x * 1000.0 << std::endl;
	f << "Y = " << info.scale.y * 1000.0 << std::endl;
	f << "Z = " << info.scale.z * 1000.0 << std::endl;
	f << "[VoxelProportion]" << std::endl;
	f << "X = " << info.scale.x * 1000.0 << std::endl;
	f << "Y = " << info.scale.y * 1000.0 << std::endl;
	f << "Z = " << info.scale.z * 1000.0 << std::endl;
}

CscanFileBuilder::CscanFileBuilder(std::string save_path, cscan_info cscan_info): save_path_(std::move(save_path)), info_(cscan_info)
{
	status_ = 1;
	_save_thread = std::thread{
		[this]()
		{
			const auto line_num = info_.raster_direction == 0 ? info_.size.y : info_.size.x;
			auto line_wrote{ 0ULL };
			try
			{
#ifdef INNER_VISION_CSCAN
				std::filesystem::create_directory(save_path_);
				// create Info file
				std::filesystem::path info_path{ save_path_ };
				info_path =  info_path.append("DataInfo.ini");
				create_info_file(info_path.generic_string(), info_);
				std::filesystem::path path{ save_path_ };
				path = path.append("Data.bin");
				save_path_ = path.generic_string();
				std::vector<uint32_t> big_endian((info_.raster_direction == 0 ? info_.size.x : info_.size.y) * info_.size.z);

#endif

				std::vector<float> buf;
				auto w = std::fstream(save_path_, std::ios::out | std::ios::binary);
				auto final = finally([&] {w.close(); });
				while (line_num > line_wrote && !canceled_)
				{
					if (_queue.wait_dequeue_timed(buf ,std::chrono::milliseconds(100)))
					{
#ifdef INNER_VISION_CSCAN
						flip_endian(reinterpret_cast<uint32_t*>(buf.data()), &big_endian[0], static_cast<int>(big_endian.size()));
						w.write(reinterpret_cast<char*>(big_endian.data()), sizeof(big_endian[0]) * big_endian.size());
#elif 
							w.write(reinterpret_cast<char*>(buf.data()),  sizeof(buf[0]) * buf.size());
#endif
						line_wrote++;
					}
					else {//timeout
					}
				}
			}
			catch (std::exception& exception)
			{
				error_message_ = std::string{exception.what()};
				status_ = 4;
				return;
			}
			if (canceled_ && line_num > line_wrote)
			{
				try {
					if (std::filesystem::exists(save_path_)) {
						std::filesystem::remove(save_path_);
					}
					status_ = 3;
				}
				catch (std::exception& exception)
				{
					error_message_ = std::string{ exception.what() };
					status_ = 4;
				}
			}
			else
			{
				status_ = 2;
			}
		}
	};
}

CscanFileBuilder::~CscanFileBuilder()
{
	if (!_save_thread.joinable()) return;
	//wait
	while (!canceled_ && _queue.size_approx() > 0 && status_ == 1)
	{
		std::this_thread::sleep_for(std::chrono::milliseconds(100));
	}
	canceled_ = true;
	if (_save_thread.joinable())
		_save_thread.join();
}

bool CscanFileBuilder::input_new_line(std::vector<float>& newline)
{
	if (!_save_thread.joinable()) return false;
	const auto line_size = (info_.raster_direction == 0 ? info_.size.x : info_.size.y) * info_.size.z;
	if (newline.size() != line_size) return false;
	return _queue.try_enqueue(newline);
}



